package Engine;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import Exception.ServiceException;
import PSQueue.AbstractPublication;
import PSQueue.AbstractQueue;
import PSQueue.PSQueue;
import Schema.SchemaVerification;
import Schema.ServiceDefinition;
import Schema.ServiceType;
import Service.AbstractParameter;
import Service.AbstractService;
import Service.JavaClass;
import Service.LocalTask;
import Service.WebService;

public class engine extends AbstractService {

	private static engine m_instance = null;
	private engine() {
	}

	public static engine getEngine() {
		if(m_instance == null) {
			m_instance = new engine();			
		}
		return m_instance;
	}
	
	private PSQueue m_queue;
	public void startEngine() {
		m_queue = new PSQueue();
		new Thread(m_queue).start();
	}
	
	public void turnoffEngine() {
		m_queue.turnOffQueue();
	}
	
	public AbstractQueue getQueue() {
		return m_queue;
	}
	
	
	//save service instances
	private HashMap<String, AbstractService> m_services = new HashMap<String, AbstractService>();
	public synchronized void loadServiceInstance(AbstractService service) {
		assert service!=null;
		String instanceID = service.getInstanceID();
		assert instanceID!=null;
		
		AbstractService saved_service = m_services.get(instanceID);
		if(saved_service!=null) {
			System.out.println("ServiceInstance " + instanceID + " has already been loaded!");
		} else {
			m_services.put(instanceID, service);
		}
	}
	
	public synchronized AbstractService getService(String instanceID) {
		assert instanceID!=null;
		return m_services.get(instanceID);
	}
	
	public synchronized void clearServiceInstance(String instanceID) {
		assert instanceID!=null;
		m_services.remove(instanceID);
	}

	
	//save deployed service definitions
	private HashMap<String, Schema.Package> deployed_services = new HashMap<String, Schema.Package>();
	public synchronized void deployService(Schema.Package pkg) {
		assert pkg!=null;
        if(SchemaVerification.isValidInstance(pkg))	{	
		   String name = pkg.getName();
		   assert name!=null;
		   deployed_services.put(name, pkg);
        } else {
        	System.out.println("Invalid Package!");
        }
	}
	
	public synchronized ServiceDefinition getServiceDefinition(String pkgName, String serviceName) {
		assert pkgName!=null;
		assert serviceName!=null;
		Schema.Package pkg = deployed_services.get(pkgName);
		if(pkg ==null) return null;
		
		List<ServiceDefinition> serviceList = pkg.getService();
		assert serviceList!=null;
		Iterator<ServiceDefinition> it = serviceList.iterator();
		while(it.hasNext()) {
			ServiceDefinition sd = it.next();
			String name = sd.getName();
			assert name!=null;
			if(serviceName.equals(name)) return sd;
		}
		return null;
	}		
	
	
	//save invocation record
	private HashMap<String, String> invocation_record = new HashMap<String, String>();
	public synchronized void addInvocationRecord(String invokerID, String invoked_serviceID) {
		assert invokerID!=null;
		assert invoked_serviceID!=null;
		invocation_record.put(invoked_serviceID, invokerID);
	}
	
	public synchronized String getInvokerServiceInstanceID(String invoked_serviceID) {
		assert invoked_serviceID!=null;
		return invocation_record.get(invoked_serviceID);
	}
	
	public synchronized ArrayList<String> getAllInvokedServiceInstanceIDs(String invokerID) {
		ArrayList<String> result = new ArrayList<String>();
		Set<String> keys = invocation_record.keySet();
		Iterator<String> it = keys.iterator();
		while(it.hasNext()) {
			String invoked_serviceName = it.next();
			String t_invokerID = invocation_record.get(invoked_serviceName);
			assert t_invokerID!=null;
			
			if(t_invokerID.equals(invokerID)) result.add(invoked_serviceName);
		}
		return result;
	}
	
	public synchronized void clearInvocationRecord(String invoked_serviceID) {
		assert invoked_serviceID!=null;
		invocation_record.remove(invoked_serviceID);
	}
	
	
	@Override
	public void OnNotification(AbstractPublication pub) {	
		assert pub!=null;
		
		if(MetaScheduleProtocol.isServiceInvocationProtocol(pub)) {
			String invoker_instanceID = MetaScheduleProtocol.getServiceInvocaterInstanceID(pub);
			assert invoker_instanceID!=null;
			
			String serviceName = MetaScheduleProtocol.getInvokedServiceName(pub);
			assert serviceName!=null;
			
			String pkgName = MetaScheduleProtocol.getPackageName(pub);
			assert pkgName!=null;
			
			AbstractParameter invocation_parameter = MetaScheduleProtocol.getProtocolParameter(pub);
			assert invocation_parameter!=null;
			
			invokeService(invoker_instanceID, pkgName, serviceName, invocation_parameter);
		} else 
		
		if(MetaScheduleProtocol.isServiceCompletionProtocol(pub)) {
			String service_instanceID = MetaScheduleProtocol.getServiceCompletionInstanceID(pub);
			assert service_instanceID!=null;
			
			AbstractParameter returned_parameter = MetaScheduleProtocol.getProtocolParameter(pub);
			assert returned_parameter!=null;						
			
			//notify invoker
			notifyServiceInvocationResult(service_instanceID, returned_parameter);
		} else 
			
		if(MetaScheduleProtocol.isServiceInvocationException(pub)) {
			String service_instanceID = MetaScheduleProtocol.getServiceExceptionSourceInstanceID(pub);
			assert service_instanceID!=null;
			
			ServiceException thrownException = MetaScheduleProtocol.getServiceExceptionObject(pub);
			assert thrownException!=null;
			
			notifyServiceInvocationException(service_instanceID, thrownException);
		}
	}

	private void notifyServiceInvocationException(String serviceInstanceID,
			ServiceException thrownException) {		
		
		AbstractService invoker_service = getInvokerService(serviceInstanceID);
		String invokedServiceName = getInvokedServiceName(serviceInstanceID);
		assert invokedServiceName!=null;
		clearInvokedServiceInstance(serviceInstanceID);
		if(invoker_service!=null) {
			invoker_service.OnException(invokedServiceName, thrownException);
		} else {
			//invoke default exception handler
			this.OnException(invokedServiceName, thrownException);
		}
	}

	private void clearInvokedServiceInstance(String serviceInstanceID) {
		assert serviceInstanceID!=null;
		clearServiceInstance(serviceInstanceID);
		
		clearInvocationRecord(serviceInstanceID);		
	}

	private AbstractService getInvokerService(String serviceInstanceID) {
		assert serviceInstanceID!=null;
		String invokerID = getInvokerServiceInstanceID(serviceInstanceID);
		if(invokerID ==null) return null;
				
		return getService(invokerID);
	}

	private void notifyServiceInvocationResult(String serviceInstanceID,
			AbstractParameter returnedParameter) {
		// TODO Auto-generated method stub
		AbstractService invoker_service = getInvokerService(serviceInstanceID);
		String invokedServiceName = getInvokedServiceName(serviceInstanceID);
		clearInvokedServiceInstance(serviceInstanceID);
		if(invoker_service!=null) {
			invoker_service.OnInvocationResult(invokedServiceName, returnedParameter);
		} else {
			//invoke default handler
			this.OnInvocationResult(invokedServiceName, returnedParameter);
		}
	}

	private String getInvokedServiceName(String serviceInstanceID) {
		assert serviceInstanceID!=null;
		AbstractService service = getService(serviceInstanceID);
		assert service!=null;
		
		return service.getServiceName();
	}

	private void invokeService(String invokerInstanceID, String pkgName, String serviceName,
			AbstractParameter invocationParameter) {
		// TODO Auto-generated method stub
		AbstractService invoked_service = loadService(pkgName, serviceName);
		if(invoked_service!=null) {
			//save the service instance
			loadServiceInstance(invoked_service);
			
			//save the invocation record
			addInvocationRecord(invokerInstanceID, invoked_service.getInstanceID());
			
			//execute the service
			invoked_service.execute(invocationParameter);
		} else {
			//return exception to invoker
			AbstractService invoker_service = getService(invokerInstanceID);
			assert invoker_service!=null;
			
			//create an exception and return it to invoker
			ServiceException thrownException = new ServiceException("");
			invoker_service.OnException(serviceName, thrownException);
		}
		
	}

	private AbstractService loadService(String pkgName, String serviceName) {
        assert pkgName!=null;
        assert serviceName!=null;
        ServiceDefinition serviceDef = getServiceDefinition(pkgName, serviceName);
        if(serviceDef == null) return null;
       
        ServiceType serviceType = serviceDef.getType();
        assert serviceType!=null;
        
        if(serviceType.equals(ServiceType.JAVA_CLASS)) {
        	JavaClass javaClass = new JavaClass();        	              	
        	//Intialize Java Class
        	javaClass.initialize(serviceDef);
        	
        	//return initialized service
        	return javaClass;
        } else
        	
        if(serviceType.equals(ServiceType.LOCAL_TASK)) {        	
        	LocalTask localTask = new LocalTask();        	        	
        	//Initialize Local Task
        	localTask.initialize(serviceDef);
        	
        	//return initialized service
        	return localTask;
        	
        } else
        	
        if(serviceType.equals(ServiceType.PROCESS))	{        	
            Service.Process process = new Service.Process();        	        	
        	//Initialize Process
            process.initialize(serviceDef);
        	
        	//return initialized service
        	return process;
        } else
        	
        if(serviceType.equals(ServiceType.WEB_SERVICE))	{
            WebService ws = new WebService();        	        	
        	//Initialize WebService
            ws.initialize(serviceDef);
        	
        	//return initialized service
        	return ws;
        } else
         
		return null;
	}

	@Override
	public void execute(AbstractParameter invocationParameter) {
		// TODO Auto-generated method stub		
	}
	
	
}
